package aqua.smile.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class ServerSocketChannelServerThread {

    private ServerSocketChannel serverChannel;
    private ServerSocket serverSocket;
    private Selector selector;
    private Thread thread;
    private static int DEFAULT_BIND_PORT = 3456;

    public ServerSocketChannelServerThread() {
        this(DEFAULT_BIND_PORT);
    }

    public ServerSocketChannelServerThread(int bindPort) {
        init(bindPort);
    }

    public void init(int bindPort) {
        try {
            //打开一个服务端通道
            serverChannel = ServerSocketChannel.open();
            //获得对等的ServerSocket对象
            serverSocket = serverChannel.socket();
            //将服务端ServerSocket绑定到指定端口
            serverSocket.bind(new InetSocketAddress(bindPort));
            System.out.println("Server listening on port: " + bindPort);
            //将通道设置为非阻塞模式
            serverChannel.configureBlocking(false);
            //打开一个选择器
            selector = Selector.open();
            //将通道注册到打开的选择器上
            serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        } catch (IOException e) {
            System.out.println("init exception: " + e);
        }
    }

    public void select() {
        try {
            //select()方法会阻塞，直到有准备就绪的通道有准备好的操作;或者当前线程中断该方法也会返回
            //这里的返回值不是选择器中已选择键集合中键的数量，而是从上一次select()方法调用到这次调用期间进入就绪状态通道的数量
            selector.select();
            //获得已选择键的集合这个集合中包含了 新准备就绪的通道和上次调用select()方法已经存在的就绪通道
            Set<SelectionKey> set = selector.selectedKeys();
            Iterator<SelectionKey> it = set.iterator();
            while (it.hasNext()) {
                SelectionKey key = (SelectionKey) it.next();
                //通过调用remove将这个键key从已选择键的集合中删除
                it.remove();
                if (key.isAcceptable()) {
                    handleAccept(key);
                } else if (key.isReadable()) {
                    handleRead(key);
                } else if (key.isWritable()) {
                    handleWrite(key);
                }

            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void handleAccept(SelectionKey key) {
        try {
            // 因为能注册SelectionKey.OP_ACCEPT事件的只有 ServerSocketChannel通道，
            // 所以这里可以直接转换成ServerSocketChannel
            ServerSocketChannel channel = (ServerSocketChannel) key.channel();
            // 获得客户端的SocketChannel对象 ,accept这里不会阻塞，如果没有连接到来，这里会返回null
            SocketChannel clientChannel = channel.accept();
            System.out.println("Accepted Connected from: " + clientChannel);
            // 将客户端socketChannel设置为非阻塞模式
            clientChannel.configureBlocking(false);

            // 为该客户端socket分配一个ByteBuffer
            // 将客户端通道的可读操作，注册到 selector 上
            SelectionKey clientKey = clientChannel.register(selector, SelectionKey.OP_READ);
            BufferFrame buffer = new BufferFrame(clientChannel, clientKey);
            clientKey.attach(buffer);

        } catch (IOException e) {
            System.out.println("handleAccept exception: " + e);
        }
    }

    public void handleRead(SelectionKey key) {
        //从键中获得相应的客户端socketChannel
        BufferFrame buffer = (BufferFrame) key.attachment();
        //从通道中读取数据 如果读取数据失败 则关闭通道 注销选择键中通道和缓冲区之间的注册关系
        if (!buffer.read()) {
            System.out.println("buffer read error");
            cleanSelectionkey(key);
        }
        //如果通道读取完数据之后 处理数据 并改变通道所注册的缓冲区的状态
        if (buffer.readCompleteFrame()) {
            //System.out.println("print data");
            buffer.printReadContent();
        }
    }

    /**
     * 通过通道将数据写会调用请求的客户端
     *
     * @param key
     */
    public void handleWrite(SelectionKey key) {
        //System.out.println("handle write");
        BufferFrame buffer = (BufferFrame) key.attachment();
        if (!buffer.write())
            cleanSelectionkey(key);
    }

    /**
     * 关闭通道 取消通道和选择器之间的注册关系
     *
     * @param key
     */
    public void cleanSelectionkey(SelectionKey key) {
        try {
            BufferFrame buffer = (BufferFrame) key.attachment();
            key.channel().close();
            key.cancel();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void serverStart() {
        //开启一个线程
        thread = new Thread(new AcceptImplThread());
        thread.start();
    }

    /**
     * 该方法时期望主线程阻塞，指导任务线程执行结束
     */
    public void joinThread() {
        try {
            thread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    class AcceptImplThread implements Runnable {

        @Override
        public void run() {
            try {
                while (true) {
                    System.out.println("select begin to run");
                    select();
                    System.out.println("select end to run");
                }
            } catch (Exception e) {
                System.out.println("run exception: " + e);
            } finally {
                try {
                    for (SelectionKey key : selector.keys()) {
                        key.channel().close();
                        key.cancel();
                    }
                } catch (Exception e2) {
                    e2.printStackTrace();
                }
            }
        }

    }

    public static void main(String[] args) {
        ServerSocketChannelServerThread server = new ServerSocketChannelServerThread();
        server.serverStart();
        server.joinThread();
        System.out.println("server exit");
    }

}
